package com.wh1200.mybatis.dao.tests

import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import com.wh1200.kotlin.only
import com.wh1200.mybatis.dao.BaseTest
import com.wh1200.xmlless.test.dao.SchoolDAO
import com.wh1200.xmlless.test.dao.ScoreDAO
import com.wh1200.xmlless.test.dao.StudentDAO
import com.wh1200.xmlless.test.dao.UserDAO
import com.wh1200.xmlless.test.form.QueryForm
import com.wh1200.xmlless.test.form.StudentQueryForm
import com.wh1200.xmlless.test.model.*
import jakarta.annotation.Resource
import org.apache.ibatis.session.SqlSessionFactory
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.BeanUtils
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull

/**
 *
 * @author 吴昊
 * @since 0.0.1
 */
class StudentDAOTest : BaseTest() {

  val deleteId = "061251173"
  val id = "061251170"
  val mobile = "17705184916"
  val name = "张三"

  @Resource
  private lateinit var dao: StudentDAO

  @Resource
  private lateinit var schoolDAO: SchoolDAO

  @Resource
  private lateinit var factory: SqlSessionFactory

  @Resource
  private lateinit var userDAO: UserDAO

  @Resource
  private lateinit var scoreDAO: ScoreDAO

  companion object {

    private val LOG: Logger = LoggerFactory.getLogger(StudentDAOTest::class.java)
  }

  @Test
  @DisplayName("从字符串数组中删除元素")
  fun removeStringFromArray() {
    val student = Student().apply {
      id = "zhangsan"
      name = "张三"
      age = 22
      favorites = listOf("旅游", "登山", "游泳")
    }
    dao.save(student)
    dao.updateFavoritesRemoveValueById(student.id!!, "游泳")
    val s2 = dao.findById(student.id!!)
    assertNotNull(s2)
    assert(!s2.favorites.contains("游泳"))
  }

  @Test
  @DisplayName("从字符串数组中删除元素")
  fun addStringFromArray() {
    val student = Student().apply {
      id = "zhangsan"
      name = "张三"
      age = 22
      favorites = listOf("旅游")
    }
    dao.save(student)
    dao.updateFavoritesAddValueById(student.id!!, "游泳")
    val s2 = dao.findById(student.id!!)
    assertNotNull(s2)
    assert(s2.favorites.contains("游泳"))
  }

  @Test
  @DisplayName("对象数组添加元素")
  fun addElementToJsonObjectArray() {
    val id = "12"
    val student = Student().apply {
      name = "张三"
      age = 22
      this.id = id
      this.tenantId = "ab"
    }
    dao.save(student)
    dao.appendData(student.id!!, StudentDetail(12))
    val studentInDb = dao.findById(student.id!!)
    assertNotNull(studentInDb)
    assertEquals(1, studentInDb.data.size)
    assertEquals(12, studentInDb.data[0].height)
    assert(
        dao.findByDataContains(mapOf("height" to 12)).isNotEmpty()
    )
  }

  @Test
  @DisplayName("测试on duplicate update, 并更新updateUserId")
  fun testDuplicateUpdate() {
    val id = "12"
    val student = Student().apply {
      name = "张三"
      age = 22
      this.id = id
    }
    dao.save(student)
    val s2 = dao.findById(id)
    assertNotNull(s2)
    dao.saveOrUpdate(Student().apply {
      name = "李四"
      age = 44
      this.id = id
    })
    val s3 = dao.findById(id)
    assertNotNull(s3)
    assertEquals(s2.id, s3.id)
    assertEquals(s2.createUserId, s3.createUserId)
    assertNotEquals(s2.updateUserId, s3.updateUserId)
    assertNotNull(s2.createUserId)
    assertNotNull(s2.updateUserId)
    assertNotNull(s3.createUserId)
    assertNotNull(s3.updateUserId)
  }

  @Test
  @DisplayName("测试自动给@CreatedBy和@LastModifiedBy注解的字段赋值")
  fun testCurrentUser() {
    val student = Student().apply {
      name = "张三"
      age = 22
    }
    dao.save(student)
    val id = student.id
    assertNotNull(id)
    val s2 = dao.findById(id)
    assertNotNull(s2)
    assertNotNull(s2.createTime)
    assertNotNull(s2.updateTime)
    assertNotNull(s2.createUserId)
    assertNotNull(s2.updateUserId)
    dao.updateNameById("李四", id)
    LOG.info(s2.createUserId.toString())
    LOG.info(s2.updateUserId.toString())
    dao.findById(id)?.run {
      LOG.info(this.updateUserId.toString())
    }
  }

  @Test
  @DisplayName("测试多条件名称")
  fun find() {
    val school = School().apply {
      this.name = "xx中学"
      this.location = "北京西路"
    }
    schoolDAO.save(school)
    val s1 = Student().apply {
      name = "张三"
      age = 22
      createTime = LocalDate.now().atStartOfDay()
      this.schoolId = school.id
    }
    val s2 = Student().apply {
      name = "李四"
      age = 36
      createTime = LocalDate.of(2022, 1, 1).atStartOfDay()
    }
    val s3 = Student().apply {
      name = "李四四"
      age = 36
      createTime = LocalDate.of(2023, 1, 1).atStartOfDay()
    }
    val u1 = User().apply {
      name = "王五"
    }
    val u2 = User().apply {
      name = "顺溜"
    }
    userDAO.save(u1)
    userDAO.save(u2)
    s1.createUserId = u1.id
    s1.updateUserId = u2.id
    s2.userId = u1.id
    s3.userId = u2.id
    val id1 = u1.id
    val id2 = u2.id
    assertNotNull(id1)
    assertNotNull(id2)
    dao.save(s1)
    dao.save(s2)
    dao.save(s3)
    val list1 = dao.find(StudentQueryForm())
    assertEquals(3, list1.size)

    assertEquals(list1.first { it.id == s1.id }.createUserName, u1.name)
    assertEquals(list1.first { it.id == s1.id }.updateUserName, u2.name)
    assertNotNull(list1.first { it.id == s1.id }.createTime)
    assert(list1.first { it.id == s1.id }.createTime!! > list1.first { it.id == s2.id }.createTime)
    assert(list1.first { it.id == s2.id }.createTime!! < list1.first { it.id == s3.id }.createTime)
    assertEquals(u1.name, list1.first { it.id == s2.id }.userName)
    assertEquals(u2.name, list1.first { it.id == s3.id }.userName)
    assertEquals(1, dao.find(StudentQueryForm(age = 22)).size)
    assertEquals(0, dao.find(StudentQueryForm(age = 23)).size)
    assertEquals(0, dao.find(StudentQueryForm(age = 22, name = "李四")).size)
    assertEquals(1, dao.find(StudentQueryForm(age = 22, name = "张")).size)
    assertEquals(1, dao.find(StudentQueryForm(keywords = "顺溜")).size)
    assertEquals(0, dao.find(StudentQueryForm(keywords = "啧啧啧")).size)
    dao.find(
        StudentQueryForm(
            keywords = "王"
        ).apply {
          type = 2
        }, 6
    )
    val list3 = dao.findVO("北京")
    assertEquals(1, list3.size)
    val matched = list3.only()
    assertEquals(school.name, matched.schoolName)
    assertEquals(school.location, matched.schoolLocation)
  }

  @Test
  @DisplayName("测试分组统计")
  fun statistics() {
    val students = listOf(Student().apply {
      grade = 1
      age = 3
    }, Student().apply {
      grade = 2
      age = 5
    }, Student().apply {
      grade = 1
      age = 4
    }, Student().apply {
      grade = 3
      age = 7
    })
    dao.saveAll(students)
    val list = dao.statistics()
    assertEquals(students.map { it.grade }.distinct().size, list.size)
    list.forEach {
      assertEquals(it.count, students.count { s -> s.grade == it.grade })
      assert(it.avgAge > 0)
      assert(it.sumAge > 0)
    }
    val list2 = dao.statistics3()
    assert(list2.isNotEmpty())
    LOG.info(list2.toString())
  }

  /**
   * 测试统计全部
   */
  @Test
  @DisplayName("统计数量")
  fun count() {
    insertStudents()
    assert(dao.count() > 0)
  }

  /**
   * 测试单条删除
   */
  @Test
  @DisplayName("根据ID删除")
  fun deleteById() {
    if (!dao.existsById(deleteId)) {
      dao.save(
          Student(
              deleteId,
              "张三",
              "13012345678", 1
          )
      )
    }
    assert(dao.existsById(deleteId))
    dao.deleteById(deleteId)
    assert(!dao.existsById(deleteId))
  }

  /**
   * 测试条件删除
   */
  @Test
  @DisplayName("根据名称删除")
  fun deleteByName() {
    val id = "testDeleteByName"
    val name = "张三"
    if (!dao.existsById(id)) {
      dao.save(
          Student(id, name, "18005184918", 1)
      )
    }
    assert(dao.existsByName(name))
    dao.deleteByName(name)
    assert(!dao.existsByName(name))
  }

  /**
   * 测试exists
   */
  @Test
  @DisplayName("id是否存在")
  fun existsById() {
    insertStudents()
    assert(!dao.existsById("4321"))
    assert(dao.existsById(id))
  }

  /**
   * 测试查询全部
   */
  @Test
  @DisplayName("查询全部")
  fun findAll() {
    if (!dao.existsById(id)) {
      dao.save(Student(id, "王五", mobile, 22))
    }
    val score = Score(80, id, 1)
    scoreDAO.save(score)
    val list = dao.findAll()
    assert(scoreDAO.findByStudentId(id).isNotEmpty())
    val spec = list.first { it.id == id }
    assert(spec.scores != null && spec.scores!!.isNotEmpty())
    assert(list.isNotEmpty())
  }

  /**
   * 测试分页查询
   */
  @Test
  @DisplayName("分页查询")
  fun findAllPageable() {
    insertStudents()
    dao.findAllPageable(
        PageRequest.of(0, 20)
    ).apply {
      this.content.map {
        it.name + " / ${it.id}"
      }.forEach { println(it) }
      println(this.content.first().name.compareTo(this.content.last().name))
    }
    dao.findAllPageable(
        PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "name"))
    ).apply {
      this.content.map {
        it.name + " / ${it.id}"
      }.forEach { println(it) }
      println(this.content.first().name.compareTo(this.content.last().name))
    }
    dao.findAllPageable(
        PageRequest.of(0, 20, Sort.by("name"))
    ).apply {
      this.content.map {
        it.name + " / ${it.id}"
      }.forEach { println(it) }
      println(this.content.first().name.compareTo(this.content.last().name))
    }
  }

  @Test
  fun findByAge() {
    dao.findByAge(12, "a")
  }

  @Test
  fun findByAgeBetween() {
    dao.saveAll(
        listOf(
            Student().apply {
              id = "a"
              age = 20
              birthday = LocalDate.of(2020, 11, 3)
            },
            Student().apply {
              id = "b"
              age = 21
              birthday = LocalDate.of(2020, 11, 3)
            },
            Student().apply {
              id = "c"
              age = 16
              birthday = LocalDate.of(2020, 11, 3)
            },
            Student().apply {
              id = "D"
              age = 24
              birthday = LocalDate.of(2020, 11, 3)
            }
        )
    )
    val list = dao.findByAgeBetweenMinAndMaxOrderByBirthday(18, 22)
    assertEquals(2, list.size)
  }

  @Test
  fun findByAgeGte() {
    dao.saveAll(
        listOf(
            Student().apply {
              id = "a"
              age = 20
              birthday = LocalDate.of(2020, 11, 3)
            },
            Student().apply {
              id = "b"
              age = 21
              birthday = LocalDate.of(2020, 11, 3)
            },
            Student().apply {
              id = "c"
              age = 16
              birthday = LocalDate.of(2020, 11, 3)
            }
        )
    )
    val list = dao.findByAgeGte(18)
    assertEquals(2, list.size)
  }

  @Test
  fun findByBirthday() {
    dao.save(
        Student().apply {
          id = "abc"
          birthday = LocalDate.of(2020, 11, 3)
        }
    )
    val students = dao.findByBirthday(
        LocalDate.of(2020, 11, 3)
    )
    students.forEach {
      println(it.createTime)
    }
    assert(students.isNotEmpty())
  }

  @Test
  fun findByCreateTimeBetween() {
    dao.saveAll(
        listOf(
            Student().apply {
              id = "a"
              age = 20
//              createTime = LocalDateTime.of(2021, 1, 3, 12, 0, 0)
            },
            Student().apply {
              id = "b"
              age = 21
//              createTime = LocalDateTime.of(2021, 1, 4, 12, 0, 0)
            },
            Student().apply {
              id = "c"
              age = 16
//              createTime = LocalDateTime.of(2021, 1, 5, 12, 0, 0)
            }
        )
    )
    val c1 = dao.findByCreateTimeBetweenStartTimeAndEndTime(
        LocalDateTime.of(2021, 1, 3, 0, 0, 0),
        LocalDateTime.of(2021, 1, 4, 13, 0, 0)
    )
    val c2 = dao.findByCreateTimeBetweenStartTimeAndEndTime(LocalDateTime.of(2021, 1, 4, 0, 0, 0), null)
    val c3 = dao.findByCreateTimeBetweenStartTimeAndEndTime(null, LocalDateTime.of(2021, 1, 4, 0, 0, 0))
    val c4 = dao.findByCreateTimeBetweenStartTimeAndEndTime(null, null)
    println(c1.size)
    println(c2.size)
    println(c3.size)
    println(c4.size)
  }

  /**
   * 测试指定值查询
   */
  @Test
  fun findByGraduatedEqTrue() {
    val list = dao.findByGraduatedEqTrue()
    println(list)
  }

  /**
   * 测试根据主键获取
   */
  @Test
  fun findById() {
    if (!dao.existsById(id)) {
      dao.save(
          Student(
              id, "张三", "18012345678", 1
          )
      )
    }
    val student = dao.findById(id)
    assertNotNull(student)
    println(student.count)
    student.scores?.forEach {
      println(it.subject)
      println(it.subject?.name + "/" + it.score)
    }
  }

  /**
   * 测试匹配字符串前缀
   */
  @Test
  fun findByPhoneNumberLikeLeft() {
    insertStudents()
    assert(dao.findByPhoneNumberLikeRight(mobile.substring(0, 4)).isNotEmpty())
    assert(dao.findByPhoneNumberLikeRight(mobile.substring(2, 6)).isEmpty())
    assert(dao.findByPhoneNumberLikeLeft(mobile.substring(mobile.length - 6, mobile.length)).isNotEmpty())
    assert(dao.findByPhoneNumberLikeLeft(mobile.substring(2, 6)).isEmpty())
  }

  @Test
  fun findByCreateTimeEqDate() {
    dao.save(
        Student("3", "李四", "17705184916", 22).apply {
          createTime = LocalDateTime.of(2021, 1, 1, 12, 0, 0)
        }
    )
    val s = dao.findByCreateTimeEqDate(LocalDate.of(2021, 1, 1))
    assertEquals(1, s.size)
    dao.deleteById("3")
  }

  @Test
  fun findByCreateTimeEqMonth() {
    dao.save(
        Student("3", "李四", "17705184916", 22).apply {
          createTime = LocalDateTime.of(2021, 1, 1, 12, 0, 0)
        }
    )
    val s = dao.findByCreateTimeEqMonth(LocalDate.of(2021, 1, 6))
    assertEquals(1, s.size)
    dao.deleteById("3")
  }

  @Test
  fun findByCreateTimeEqMonth2() {
    dao.save(
        Student("3", "李四", "17705184916", 22).apply {
          createTime = LocalDateTime.of(2021, 1, 1, 12, 0, 0)
        }
    )
    val s = dao.findByCreateTimeEqMonth(LocalDate.of(2021, 1, 6))
    val s2 = dao.findByCreateTimeEqMonth(LocalDate.of(2021, 2, 6))
    assertEquals(1, s.size)
    assertEquals(0, s2.size)
    dao.deleteById("3")
  }

  /**
   * 测试匹配字符串后缀
   */
  @Test
  fun findByPhoneNumberLikeRight() {
    dao.save(
        Student("2", "李四", "17705184916", 22)
    )
    assert(dao.findByPhoneNumberLikeLeft("4916").isNotEmpty())
    assert(dao.findByPhoneNumberLikeLeft("180").isEmpty())
    dao.deleteById("2")
  }

  @Test
  fun find777() {
    dao.saveAll(
        listOf(
            Student().apply {
              this.tenantId = "cd"
              this.detail = StudentDetail(
                  roles = listOf(1, 2),
                  names = listOf("a", "b")
              )
            },
            Student().apply {
              this.tenantId = "cd"
              this.detail = StudentDetail(
                  roles = listOf(3, 4),
                  names = listOf("c", "d")
              )
            }
        )
    )
    val list = dao.findContainsAny(listOf(1, 3, 5))
    assert(list.isNotEmpty())
    val list2 = dao.findContainsInt(1)
    assert(list2.isNotEmpty())
    val list3 = dao.findContainsString("a")
    assert(list3.isNotEmpty())
    dao.findByJsonField()
  }

  @Test
  fun findInObjectArray() {
    dao.findInObjectArray()
  }

  @Test
  fun testFunction() {
    dao.save(
        Student("2", "李四", "17705184916", 22)
            .apply {
              createTime = LocalDateTime.of(2021, 1, 1, 12, 0, 0)
              updateTime = LocalDateTime.of(2021, 2, 1, 12, 0, 0)
            }
    )
    val result = dao.selectAgeSum()
    println(result)
  }

  @Test
  fun findByState() {
    dao.saveAll(
        listOf(
            Student("a", "张三", "17705184916", 22, StudentState.Abnormal),
            Student("b", "李四", "17705184916", 22)
        )
    )
    val list = dao.findByStateIn(listOf(StudentState.Normal))
    assertEquals(1, list.size)
  }

  /**
   * 测试分页带条件查询
   */
  @Test
  @DisplayName("使用spring data Pageable对象传递分页参数，使用Page对象接收数据")
  fun findPage() {
    dao.saveAll(createBatchStudents(100))
    val page = dao.findAllPage(
        null, null,
        PageRequest.of(0, 20)
    )
    assertEquals(20, page.content.size)
    println(page.totalElements)
  }

  @Test
  @DisplayName("使用mybatis-plus的IPage对象分页，使用xml配置sql")
  fun findPage2() {
    dao.saveAll(createBatchStudents(100))
    val page = dao.findAllPage2(
        Page(0, 20)
    )
    assertEquals(20, page.records.size)
    assertEquals(100, page.total)
  }

  @Test
  @DisplayName("使用mybatis-plus的IPage对象分页，不适用xml配置，利用xmlless生成sql")
  fun findPage3() {
    dao.saveAll(createBatchStudents(100))
    val page = dao.findAllPage3(
        null, null,
        Page(0, 20)
    )
    assertEquals(20, page.records.size)
    assertEquals(100, page.total)
  }

  @Test
  fun find4() {
    dao.find4(StudentQueryForm())
    dao.find5(1, "测试")
    dao.find6(2, "测试")
  }

  @Test
  @DisplayName("使用xml文件配置sql，使用spring data的Pageable对象传递分页参数")
  fun findPage4() {
    dao.saveAll(createBatchStudents(100))
    val page = dao.findAllPage4(
        null, null,
        PageRequest.of(0, 20)
    )
    assertEquals(20, page.content.size)
    assertEquals(100, page.totalElements)
  }

  @Test
  @DisplayName("测试从复杂参数中取条件值")
  fun findByNameLikeAndAgeAndCreateTimeBetweenStartAndEnd() {
    dao.findByNameLikeAndAgeAndCreateTimeBetweenStartAndEnd(
        QueryForm(
            name = "张三",
            age = 22
        )
    )
  }

  @Test
  @DisplayName("测试从复杂参数中取条件值并分页，分页参数不添加@Param")
  fun findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable() {
    dao.findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable(
        QueryForm(
            name = "张三",
            age = 22
        ),
        PageRequest.of(0, 10)
    )
  }

  @Test
  @DisplayName("测试从复杂参数中取条件值并分页, 分页参数添加@Param")
  fun findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable2() {
    dao.save(
        Student(name = "张三", age = 22)
    )
    val res = dao.findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable2(
        QueryForm(
            name = "张三",
            age = 22
        ),
        PageRequest.of(0, 10)
    )
    assertEquals(1, res.content.size)
    assertEquals(1, res.totalElements)
  }

  @Test
  @DisplayName("测试从复杂参数中取条件值并分页，分页参数不添加@Param")
  fun findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable3() {
    dao.findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable3(
        QueryForm(
            name = "张三",
            age = 22
        ),
        PageRequest.of(0, 10)
    )
  }

  @Test
  fun findByNameOrAge() {
    dao.save(
        Student(
            "6", "666",
            "17705184916", 22
        ).apply {
          this.age = 55
        }
    )
//    val s0 = dao.findByNameOrAge(QueryForm(name = "777", age = 55))
    val s1 = dao.findByNameOrAge(QueryForm(age = 55))
//    val s2 = dao.findByNameOrAge(QueryForm(name = "666"))
//    val s3 = dao.findByNameOrAge(QueryForm(name = "777"))
//    assertEquals(1, s0.size)
    assertEquals(1, s1.size)
//    assertEquals(1, s2.size)
//    assertEquals(0, s3.size)
  }

  @Test
  fun findByAgeBetweenNullable() {
    dao.saveAll(
        listOf(
            Student(
                "6", "666",
                "17711110000", 22
            ).apply {
              this.age = 55
            },
            Student(
                "9", "666",
                "17700001111", 22
            ).apply {
              this.age = 59
            }
        )
    )
    assertEquals(1, dao.findByAgeBetween(54, 56).size)
    assertEquals(0, dao.findByAgeBetween(56, 58).size)
    assertEquals(1, dao.findByAgeBetween(0, 58).size)
    assertEquals(2, dao.findByAgeBetween(54, Int.MAX_VALUE).size)
    assertEquals(1, dao.findByAgeBetween(56, Int.MAX_VALUE).size)
    assertEquals(0, dao.findByAgeBetween(0, 52).size)
    assertEquals(2, dao.findByAgeBetween(0, Int.MAX_VALUE).size)
  }

  @Test
  fun getJsonObject() {
    dao.save(
        Student("3", "李四", "17705184916", 22).apply {
          detail = StudentDetail(170)
        }
    )
    val s = dao.findDetail()
    assertEquals(1, s.size)
    dao.deleteById("3")
    println(s)
  }

  /**
   * 测试单条插入
   */
  @Test
  fun save() {
    dao.deleteById(id)
    assert(!dao.existsById(id))
    dao.save(
        Student(
            id,
            "张三",
            "18012345678", 1
        ).apply {
          detail = StudentDetail(172)
          favorites = listOf("旅游", "登山")
        })
    val bean = dao.findById(id)
    assertNotNull(bean?.detail)
    assertEquals(bean?.detail?.height, 172)
    assert(dao.existsById(id))
    println(bean?.favorites)
  }

  /**
   * 测试根据关联表中的字段查询
   */
  @Test
  fun findBySubjectId() {
    insertStudents()
    val students = dao.findBySubjectId(1)
    assert(students.isNotEmpty())
    factory.configuration.typeAliasRegistry.resolveAlias<Any>("Student")
  }

  /**
   * 测试批量插入及批量删除
   */
  @Test
  @DisplayName("测试批量删除")
  fun saveAllAndDeleteAll() {
    val id1 = "saveAll1"
    val id2 = "saveAll2"
    if (dao.existsById(id1)) {
      dao.deleteById(id1)
    }
    if (dao.existsById(id2)) {
      dao.deleteById(id2)
    }
    dao.saveAll(
        listOf(
            Student(id1, "zs", "123", 1),
            Student(id2, "zs", "123", 1)
        )
    )
    assert(dao.existsById(id1))
    assert(dao.existsById(id2))
    dao.deleteByIds(listOf(id1, id2))
    assert(!dao.existsById(id1))
    assert(!dao.existsById(id2))
  }

  /**
   * 测试新增或更新
   */
  @Test
  fun saveOrUpdate() {
    dao.saveOrUpdate(
        Student(
            id,
            "张三",
            "13012345678", 1
        ).apply {
          detail = StudentDetail(172)
          favorites = listOf("旅游", "登山")
        })
  }

  /**
   * 测试新增或更新
   */
  @Test
  fun saveOrUpdateAll() {
    val student1 = Student(
        id, "李四", "18012345678", 1
    ).apply {
      email = "aaa@a.com"
      detail = StudentDetail(172)
      favorites = listOf("旅游", "登山")
    }
    val student3 = Student(
        "aaa", "王五", "18055555555", 2
    )
    val student2 = Student()
    BeanUtils.copyProperties(student1, student2)
    student2.name = "张三"
    student2.email = null
    dao.save(student1)
    dao.saveOrUpdateAll(listOf(student2, student3))
    val student4 = dao.findById(id)
    assertNotNull(student4)
    assertNotEquals(student1.name, student4.name)
    val student5 = dao.findById(student3.id!!)
    assertNotNull(student5)
  }

  @Test
  @DisplayName("测试分组查询")
  fun testGroupBy() {
    dao.saveAll(
        listOf(
            Student(grade = 1),
            Student(grade = 2),
            Student(grade = 2),
            Student(grade = 3),
            Student(grade = 1),
            Student(grade = 1)
        )
    )
    val list = dao.findCountAndGradeGroupByGrade()
    assertEquals(3, list.size)
    assertEquals(3, list.first { it.grade == 1 }.count)
    assertEquals(2, list.first { it.grade == 2 }.count)
    assertEquals(1, list.first { it.grade == 3 }.count)
  }

  @Test
  @DisplayName("测试Having过滤")
  fun testHaving() {
    dao.saveAll(
        listOf(
            Student(grade = 1),
            Student(grade = 2),
            Student(grade = 2),
            Student(grade = 3),
            Student(grade = 1),
            Student(grade = 1)
        )
    )
    println(
        dao.findAll().groupBy { it.grade }.mapValues {
          it.value.size
        }
    )
    val list = dao.findCountAndGradeGroupByGradeHavingCountGt1()
    assertEquals(2, list.size)
    assertEquals(3, list.first { it.grade == 1 }.count)
    assertEquals(2, list.first { it.grade == 2 }.count)
  }

  /**
   * 测试更新
   */
  @Test
  @DisplayName("测试更新返回值")
  fun update() {
    insertStudents()
    assertEquals(
        1,
        dao.update(
            Student(
                id, "张三",
                "17712345678",
                9
            )
        )
    )
    assertEquals(
        0,
        dao.update(
            Student(
                id + "abc", "张三",
                "17712345678",
                9
            )
        )
    )
  }

  /**
   * 测试更新单个属性
   */
  @Test
  @DisplayName("测试更新指定字段")
  fun updateNameById() {
    val id = "testUpdateNameById"
    val oldName = "oldName"
    val newName = "newName"
    if (dao.existsById(id)) {
      dao.deleteById(id)
    }
    dao.save(
        Student(
            id,
            oldName,
            "18012345678", 1
        )
    )
    assertEquals(oldName, dao.findById(id)?.name)
    assertEquals(1, dao.updateNameById(newName, id))
    assertEquals(newName, dao.findById(id)?.name)
    dao.deleteById(id)
  }

  private fun insertStudents() {
    dao.saveAll(
        (0..10).map {
          Student("${it}00$it", "$it", "$it", 1)
        }
    )
    dao.save(
        Student(id, name, mobile, 1)
    )
    val score = Score(80, id, 1)
    scoreDAO.save(score)
  }

}
